/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.worker.business.controller;

import static org.ourgrid.worker.WorkerConstants.ENV_PLAYPEN;
import static org.ourgrid.worker.WorkerConstants.ENV_STORAGE;

import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

import org.ourgrid.broker.business.dao.WorkerDAO;
import org.ourgrid.common.executor.ExecutorException;
import org.ourgrid.common.executor.ExecutorHandle;
import org.ourgrid.common.internal.IResponseTO;
import org.ourgrid.common.internal.OurGridResponseControl;
import org.ourgrid.common.internal.response.LoggerResponseTO;
import org.ourgrid.reqtrace.Req;
import org.ourgrid.worker.WorkerConstants;
import org.ourgrid.worker.business.controller.patdown.WorkerExecutionInfo;
import org.ourgrid.worker.business.dao.EnvironmentDAO;
import org.ourgrid.worker.business.dao.ExecutionDAO;
import org.ourgrid.worker.business.dao.WorkAccountingDAO;
import org.ourgrid.worker.business.dao.WorkerDAOFactory;
import org.ourgrid.worker.business.dao.WorkerStatusDAO;
import org.ourgrid.worker.business.messages.ExecutionControllerMessages;
import org.ourgrid.worker.communication.dao.FutureDAO;
import org.ourgrid.worker.communication.sender.ExecutorKillCommandSender;
import org.ourgrid.worker.response.CancelBeginAllocationActionResponseTO;
import org.ourgrid.worker.response.CancelExecutionActionResponseTO;
import org.ourgrid.worker.response.ExecutorKillCommandResponseTO;
import org.ourgrid.worker.response.ExecutorKillPreparingAllocationResponseTO;
import org.ourgrid.worker.response.SubmitExecutionActionResponseTO;
import org.ourgrid.worker.response.SubmitPrepareAllocationActionResponseTO;

import br.upe.dsc.patdown.control.PDController;
import br.upe.dsc.patdown.control.PDWorkerInfo;

public class ExecutionController {

	private static ExecutionController instance = null;

	@Req("REQ084")
	public static synchronized ExecutionController getInstance() {
		if (instance == null) {
			instance = new ExecutionController();
		}
		return instance;
	}

	public void beginAllocation(List<IResponseTO> responses) {

		responses.add(new LoggerResponseTO(ExecutionControllerMessages.getPrepareAllocationActionStartedMessage(),
				LoggerResponseTO.DEBUG));

		WorkerDAOFactory.getInstance().getWorkerStatusDAO().setPreparingAllocationState(true);
		submitBeginAllocationAction(responses);
	}

	private void submitBeginAllocationAction(List<IResponseTO> responses) {
		SubmitPrepareAllocationActionResponseTO to = new SubmitPrepareAllocationActionResponseTO();
		responses.add(to);
	}

	@Req("REQ084")
	public void scheduleCommand(long requestID, Map<String, String> envVars, String command, String senderPublicKey,
			List<IResponseTO> responses, boolean isExecutionClientDeployed) {

		// patdown
		boolean startNew;

		if (startNew = PDWorkerInfo.shouldStartNew(requestID, senderPublicKey)) {
			if (PDWorkerInfo.NEW_WORK) {
				// WorkerController.getInstance().cleanWorker(responses);
			}
			System.out.println("PATDOWN: Starting up new execution");
			PDWorkerInfo.setRequestIdExecuting(requestID);
			PDWorkerInfo.setBrokerInfoExecuting(senderPublicKey);

			ExecutorHandle handle = (ExecutorHandle) PDWorkerInfo.getExecutionReference();
			/*
			 * ExecutorHandle handle =
			 * WorkerDAOFactory.getInstance().getExecutionDAO
			 * ().getCurrentHandle();
			 */
			if (handle != null) {
				try {
					WorkerDAOFactory.getInstance().getExecutorDAO().getExecutor().killCommand(handle);
				} catch (ExecutorException e) {
					// TODO log
				}
				finally{
					PDWorkerInfo.setExecutionReference(null);
				}
			}
		} else {
			// return;
			System.out.println("PATDOWN: Shouldn't start a new execution. Could still be executing.");

			WorkerDAOFactory.getInstance().getExecutionDAO().setCurrentHandle((ExecutorHandle) PDWorkerInfo.getExecutionReference());
			Thread t = new Thread(new Runnable(){
				public void run() {
					WorkerExecutionInfo.fetchExecution((ExecutorHandle) PDWorkerInfo.getExecutionReference());
				}
			});
			t.start();
		}
		// -------

		ExecutionDAO executionDAO = WorkerDAOFactory.getInstance().getExecutionDAO();
		WorkerStatusDAO workerStatusDAO = WorkerDAOFactory.getInstance().getWorkerStatusDAO();
		EnvironmentDAO environmentDAO = WorkerDAOFactory.getInstance().getEnvironmentDAO();
		WorkAccountingDAO accountingDAO = WorkerDAOFactory.getInstance().getWorkAccountingDAO();

		if (startNew || true) {// patdown condition
			envVars.put(WorkerConstants.ENV_PLAYPEN, environmentDAO.getPlaypenDir());
			envVars.put(WorkerConstants.ENV_STORAGE, environmentDAO.getStorageDir());
		}// patdown condition end

		if (executionDAO.getCurrentHandle() == null && !executionDAO.isExecutionFinished()) {

			if (workerStatusDAO.isAllocatedForRemotePeer()) {
				accountingDAO.getActualWorkAccounting().startCPUTiming();
			}

			// patdown
			System.out.println("Submiting command: " + command);
			// -------

			submitExecutionAction(envVars, command, responses);

			responses.add(new LoggerResponseTO(ExecutionControllerMessages.getScheduleExecutionMessage(command,
					requestID, envVars, senderPublicKey), LoggerResponseTO.INFO));

		} else {
			responses.add(new LoggerResponseTO(ExecutionControllerMessages.getConcurrentExecutionMethod(requestID,
					command, envVars), LoggerResponseTO.ERROR));
		}

	}

	public String solvePlaypenAndStorage(String command, List<IResponseTO> responses) {
		if (command != null) {

			String newCommand = command;
			String playpenDir = getPlaypenDir(responses);

			if (playpenDir != null) {
				newCommand = newCommand.replace("$" + ENV_PLAYPEN, playpenDir);
			}

			String storageDir = getStorageDir(responses);

			if (storageDir != null) {
				newCommand = newCommand.replace("$" + ENV_STORAGE, storageDir);
			}

			return newCommand;
		}

		return command;
	}

	public void cancelActiveExecution(List<IResponseTO> responses, boolean interruptWorking,
			boolean cancelPreparingAllocation) {

		if (interruptWorking) {

			ExecutionDAO executionDAO = WorkerDAOFactory.getInstance().getExecutionDAO();
			ExecutorHandle currentHandle = executionDAO.getCurrentHandle();

			if (currentHandle != null) {
				responses.add(new CancelExecutionActionResponseTO());
				executionDAO.setCurrentHandle(null);

				// patdown
				System.out.println("we will be canceling execution: " + currentHandle.toString());

				// -------

				ExecutorKillCommandResponseTO executorKillCommandResponseTO = new ExecutorKillCommandResponseTO();
				executorKillCommandResponseTO.setHandle(currentHandle);

				// patdown - canceling requesting kill
				PDController.IS_DEBUG = PDController.IS_DEBUG || false;
				System.out.println("Should be adding the executorKillCommandResponseTO here.");
				PDWorkerInfo.setExecutionReference(currentHandle);
				//responses.add(executorKillCommandResponseTO);
				// -------
			}

			if (cancelPreparingAllocation) {

				if (hasActivePreparingAllocation()) {
					System.out.println("Cancelling preparing allocation");
					
					cancelPreparingAllocationExecution(responses);
					executionDAO.setExecutingKillPreparingAllocation(true);

					ExecutorKillPreparingAllocationResponseTO executorKillPreparingAllocationResponseTO = new ExecutorKillPreparingAllocationResponseTO();
					responses.add(executorKillPreparingAllocationResponseTO);
				}
			}
		}
	}

	private boolean hasActivePreparingAllocation() {
		FutureDAO futureDAO = WorkerDAOFactory.getInstance().getFutureDAO();
		Future<?> beginAllocationFuture = futureDAO.getBeginAllocationFuture();
		return beginAllocationFuture != null;
	}

	public void executionFinish(boolean success) {
		ExecutionDAO executionDAO = WorkerDAOFactory.getInstance().getExecutionDAO();
		executionDAO.setCurrentHandle(null);
		executionDAO.setExecutionFinished(success);
	}

	public void executionIsRunning(ExecutorHandle handle) {
		WorkerDAOFactory.getInstance().getExecutionDAO().setCurrentHandle(handle);
	}

	@Req("REQ084")
	private void submitExecutionAction(Map<String, String> envVars, String command, List<IResponseTO> responses) {

		SubmitExecutionActionResponseTO to = new SubmitExecutionActionResponseTO();
		to.setCommand(command);
		to.setEnvVars(envVars);

		responses.add(to);
	}

	private String getPlaypenDir(List<IResponseTO> responses) {
		return WorkerDAOFactory.getInstance().getEnvironmentDAO().getPlaypenDir();
	}

	private String getStorageDir(List<IResponseTO> responses) {
		return WorkerDAOFactory.getInstance().getEnvironmentDAO().getStorageDir();
	}

	public void cancelPreparingAllocationExecution(List<IResponseTO> responses) {

		responses.add(new LoggerResponseTO(ExecutionControllerMessages.getPrepareAllocationActionCancelledMessage(),
				LoggerResponseTO.DEBUG));
		responses.add(new CancelBeginAllocationActionResponseTO());
	}
}
